#include <lcthw/darray.h>
#include <assert.h>

DArray *DArray_create(size_t element_size, size_t initial_max)
{
    DArray *array = malloc(sizeof(DArray));
    check_mem(array);
    array->max = initial_max;
    check(array->max > 0, "You must set an initial_max > 0.");
    array->contents = calloc(initial_max, sizeof(void *));
    check_mem(array->contents);
    array->end = 0;
    array->element_size = element_size;
    array->expand_rate = DEFAULT_EXPAND_RATE;
    return array;
error:
    if (array)
    {
        if (array->contents)
        {
            free(array->contents);
        }
        free(array);
    }
    return NULL;
}

void DArray_clear(DArray *array)
{
    int i = 0;
    check(array != NULL, "Invalid array.");
    for (i = 0; i < array->max; i++)
    {
        if (array->contents[i] != NULL)
        {
            free(array->contents[i]);
        }
    }
error:
    return;
}

static inline int DArray_resize(DArray *array, size_t newsize)
{
    check_mem(array);
    array->max = newsize;
    check(array->max > 0, "The newsize must be > 0.");
    void *contents = realloc(array->contents, array->max * sizeof(void *)); // Reallocate the memory for the contents in the array
    check_mem(contents);
    array->contents = contents;
    return 0;
error:
    return -1;
}

int DArray_expand(DArray *array)    // Expand the array, each time the array is expanded, the size of the array is increased by the expand_rate
{
    check_mem(array);
    size_t old_max = array->max;
    check(DArray_resize(array, array->max + array->expand_rate) == 0, "Failed to expand array to new size: %d", array->max + (int)array->expand_rate);
    memset(array->contents + old_max, 0, array->expand_rate * sizeof(void *)); // Set the contents of the expanded array to 0
error:
    return -1;
}

int DArray_contract(DArray *array)      // Contract the array, to decrease to the number of end or the expand_rate(depending on which is greater)
{
    check(array != NULL, "Invalid array.");
    int new_size = array->end < (int)array->expand_rate ? (int)array->expand_rate : array->end; // Set the new size to the expand_rate if the end is less than the expand_rate
    return DArray_resize(array, new_size);

error:
    return -1;
}

void DArray_destroy(DArray *array)
{
    if (array)
    {
        if (array->contents)
        {
            free(array->contents);
        }
        free(array);
    }
}

void DArray_clear_destroy(DArray *array)
{
    DArray_clear(array);
    DArray_destroy(array);
}

int DArray_push(DArray *array, void *el)
{
    check(array != NULL, "Invalid array.");
    check(el != NULL, "Invalid element.");
    array->contents[array->end] = el;
    array->end++;
    if (DArray_end(array) >= DArray_max(array))
    {
        return DArray_expand(array);
    }
    else
    {
        return 0;
    }
error:
    return -1;
}

void *DArray_pop(DArray *array)
{
    check(array->end - 1 >= 0, "Attempt to pop from empty array.");
    void *el = DArray_remove(array, array->end - 1);
    array->end--;
    if (DArray_end(array) >= (int)array->expand_rate && DArray_end(array) % array->expand_rate == 0)    // If the end is greater than or equal to the expand_rate and the end is divisible by the expand_rate
    {
        DArray_contract(array);
    }

    return el;
error:
    return NULL;
}